﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Expect.Core.BaseClasses;

namespace Expect.Core.Reports
{
    using System.Reflection;
    using Expect.Core.Interfaces;

    class NamespaceTreeItem
    {
        private string _metaName;

        public string Namespace { get; set; }

        public string Name
        {
            get
            {
                if (_metaName != null) return _metaName;

                if (string.IsNullOrEmpty(Namespace)) return null;

                return Namespace.Substring(Namespace.LastIndexOf(".") + 1);
            }
        }

        public NamespaceTreeItem Parent { get; set; }

        private List<NamespaceTreeItem> _childs = new List<NamespaceTreeItem>();

        public List<NamespaceTreeItem> Childs
        {
            get
            {
                return this._childs;
            }
            set
            {
                this._childs = value;
            }
        }

        public IEnumerable<Type> Types { get; set; }

        public Dictionary<string, Type> TypesWithSpecs { get; private set; }

        public ReportMetadataBase ReportMetadata { get; set; }

        public void AddChild(NamespaceTreeItem item)
        {
            item.Parent = this;
            Childs.Add(item);
        }

        public void Initialize()
        {
            InitMetadata();
            this.InitTypesWithSpecs();
        }

        private void InitMetadata()
        {
            var reportdataType = this.Types.FirstOrDefault(t => t.Name == "ReportMetadata");
            if (reportdataType != null && reportdataType.IsSubclassOf(typeof(ReportMetadataBase)))
            {
                var attr = reportdataType.GetCustomAttributes(typeof(ReportMetadataAttribute), true);
                if (attr != null && attr.Length > 0)
                {
                    this._metaName = (attr[0] as ReportMetadataAttribute).Name;
                }
                ReportMetadata = (ReportMetadataBase)Activator.CreateInstance(reportdataType);
                ReportMetadata.Initialize();
            }
        }

        public void AddToSpecification(SpecificationDefinition spec)
        {
            if (!this.HasChildTypesWithSpecs()) return;

            SpecificationDefinition itemSpec = spec;
            if (!string.IsNullOrEmpty(Name))
            {
                itemSpec = new SpecificationDefinition();
                itemSpec.Name = this.Name;
                spec.AddChildSpec(itemSpec);
            } 

            if (ReportMetadata != null)
            {
                this.AddToSpecificationByMetadata(itemSpec);
            }
            else
            {
                this.AddToSpecificationByDefault(itemSpec);
            }
        }

        private void AddToSpecificationByMetadata(SpecificationDefinition spec)
        {                       
            foreach (var item in ReportMetadata)
            {
                if (item.Kind == ReportItemKind.File)
                {
                    if (TypesWithSpecs.ContainsKey(item.Name))
                    {
                        var type = TypesWithSpecs[item.Name];
                        this.AddTypeTo(item.Name, type, spec);
                    }
                    else
                    {
                        Console.WriteLine("Item \"" + item.Name + "\" not found");
                    }
                }
                if (item.Kind == ReportItemKind.Folder)
                {
                    var nsName = this.ConvertToNamespace(item.Name);
                    var child = Childs.Find(x => x.Name == item.Name || string.Compare(x.Name, nsName, true) == 0);
                    if (child != null)
                    {
                        child.AddToSpecification(spec);
                    }
                    else
                    {
                        Console.WriteLine("Item folder \"" + item.Name + "\" not found");
                    }
                }
            }
        }

        private string ConvertToNamespace(string input)
        {
            input = DefaultNameProvider.RemoveDiacritics(input);
            StringBuilder sb = new StringBuilder();
            foreach (var c in input)
            {
                if (Char.IsLetterOrDigit(c))
                {
                    sb.Append(c);
                }
                else
                {
                    sb.Append("_");
                }
            }
            return sb.ToString();
        }

        private void AddToSpecificationByDefault(SpecificationDefinition spec)
        {
            if (TypesWithSpecs != null)
            {
                foreach (var type in TypesWithSpecs)
                {
                    this.AddTypeTo(type.Key, type.Value, spec);
                }
            }

            foreach (var item in Childs)
            {
                item.AddToSpecification(spec);
            }
        }

        private void AddTypeTo(string name, Type t, SpecificationDefinition parent)
        {
            SpecificationDefinition typespec;

            var specmethods = t.GetMethods();
            ISpecificationContainer instance = (ISpecificationContainer)Activator.CreateInstance(t);
            instance.InitializeContext();
            instance.Context.ResolveOnly = true;
            var setups = AddMethods<SetupProviderAttribute>(t, specmethods, instance, true);
            var methods = AddMethods<SpecificationProviderAttribute>(t, specmethods, instance, false);
            var teardowns = AddMethods<TeardownProviderAttribute>(t, specmethods, instance, true);

            if (setups.Count() == 0 && teardowns.Count() == 0 && methods.Count() == 1)
            {
                typespec = parent;
            }
            else
            {
                typespec = new SpecificationDefinition();
                typespec.Name = name;
                parent.AddChildSpec(typespec);
            }

            foreach (var setup in setups) typespec.AddChildSpec(setup);
            foreach (var method in methods) typespec.AddChildSpec(method);
            foreach (var teardown in teardowns) typespec.AddChildSpec(teardown);
        }

        private IEnumerable<SpecificationDefinition> AddMethods<T>(Type t, MethodInfo[] specmethods, object instance, bool onlyone)
        {
            foreach (var methodInfo in specmethods)
            {
                var attr = methodInfo.GetCustomAttributes(typeof(T), true);
                if (attr != null && attr.Length > 0)
                {
                    var metadata = attr[0] as SpecificationProviderAttribute;
                    SpecificationDefinition methodspec = new SpecificationDefinition();
                    methodInfo.Invoke(instance, new object[] {methodspec});
                    methodspec.Run();
                    yield return methodspec;
                    if (onlyone) yield break;
                }
            }
        }

        private bool HasChildTypesWithSpecs()
        {
            if (TypesWithSpecs != null && TypesWithSpecs.Count > 0) return true;

            foreach (var child in Childs)
            {
                if (child.HasChildTypesWithSpecs()) return true;
            }

            return false;
        }

        private void InitTypesWithSpecs()
        {
            Dictionary<string, Type> elements = new Dictionary<string, Type>();
            foreach (var type in Types)
            {
                var attr = type.GetCustomAttributes(typeof(SpecificationContainerAttribute), true);
                if (attr != null && attr.Length > 0)
                {
                    string name = (attr[0] as SpecificationContainerAttribute).Name;
                    if (!string.IsNullOrEmpty(name))
                    {
                        elements.Add(name, type);
                    }
                }
            }

            if (elements.Count > 0)
            {
                TypesWithSpecs = elements;
            }
        }

    }
}
